package xin.marcher.module.common.utils.message;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import xin.marcher.module.common.domain.BinlogData;
import xin.marcher.module.common.domain.TraceData;

import java.util.*;

/**
 * MySQL binlog解析工具类
 */
public abstract class BinlogUtils {

    /**
     * 解析binlog json字符串
     *
     * @param binlogStr binlog json字符串
     * @return BinlogData
     */
    public static BinlogData getBinlogData(String binlogStr) {
        // isJson方法里面会判断字符串是不是为空，所以这里不需要重复判断
        if (JSONUtil.isJson(binlogStr)) {
            JSONObject binlogJson = JSONUtil.parseObj(binlogStr);
            // 不处理DDL的binlog，只处理数据变更
            if (binlogJson.getBool("isDdl")) {
                return null;
            }
            BinlogData binlogData = new BinlogData();
            // 表名
            String tableName = binlogJson.getStr("table");
            binlogData.setTableName(tableName);
            // 操作类型
            String operateType = binlogJson.getStr("type");
            binlogData.setOperateType(operateType);
            // 操作时间
            Long operateTime = binlogJson.getLong("ts");
            binlogData.setOperateTime(operateTime);

            // data数据
            // 这条 sql 可能变更的是多条数据, 封装成数组
            JSONArray dataArray = binlogJson.getJSONArray("data");

            List<Map<String, Object>> dataMap = jsonArrayToMapList(dataArray);

            binlogData.setDataMap(dataMap);

            if (!binlogJson.isNull("old")) {
                // old数据
                JSONArray oldArray = binlogJson.getJSONArray("old");

                List<Map<String, Object>> oldMap = jsonArrayToMapList(oldArray);

                binlogData.setOldMap(oldMap);

            }

            return binlogData;
        }
        return null;
    }

    private static List<Map<String, Object>> jsonArrayToMapList(JSONArray jsonArray) {
        if (null != jsonArray) {
            Iterable<JSONObject> arrayIterator = jsonArray.jsonIter();
            // 遍历data节点或old节点并返回Map
            if (null != arrayIterator) {
                // binlog的data数组或old数组里数据的类型为Map
                List<Map<String, Object>> dataMap = new ArrayList<>();
                while (arrayIterator.iterator().hasNext()) {
                    JSONObject jsonObject = arrayIterator.iterator().next();
                    Map<String, Object> data = new HashMap<>(jsonObject.size());
                    jsonObject.keySet().forEach(key -> {
                        data.put(key, jsonObject.get(key));
                    });
                    dataMap.add(data);
                }
                return dataMap;
            }
        }
        return null;
    }

    /**
     * 把binlog json字符串解析成TraceData
     */
    public static List<TraceData> getTraceData(String binlogStr) {
        if (!JSONUtil.isJson(binlogStr)) {
            return null;
        }

        List<TraceData> traceDataList = new ArrayList<>();
        JSONObject jsonObject = JSONUtil.parseObj(binlogStr);

        // 表名
        String tableName = jsonObject.getStr("table");

        // 所有的新数据
        JSONArray dataArray = jsonObject.getJSONArray("data");
        List<Map<String, Object>> newDataList = BinlogUtils.jsonArrayToMapList(dataArray);

        // 变化了的旧数据
        List<Map<String, Object>> changedOldDataList = null;
        if (!jsonObject.isNull("old")) {
            dataArray = jsonObject.getJSONArray("old");
            changedOldDataList = BinlogUtils.jsonArrayToMapList(dataArray);
        }

        int size = newDataList.size();
        for (int i = 0; i < size; i++) {
            TraceData traceData = new TraceData();
            traceData.setTableName(tableName);

            // newData里面有所有的字段
            Map<String, Object> newData = newDataList.get(i);
            traceData.setNewData(newData);

            if (changedOldDataList != null) {
                // oldData里面只有变化了的字段
                Map<String, Object> changedOldData = changedOldDataList.get(i);
                Map<String, Object> oldData = new HashMap<>();
                newData.forEach(oldData::put);
                changedOldData.forEach(oldData::put);
                traceData.setOldData(oldData);
            } else {
                traceData.setOldData(Collections.emptyMap());
            }
            traceDataList.add(traceData);
        }

        return traceDataList;
    }
}
